Expand description
Embed an Info.plist
or launchd.plist
file directly in your
executable binary, brought to you by @NikolaiVazquez!
If you found this library useful, please consider sponsoring me on GitHub. ❤️
§Index
- Motivation
- Usage
- Minimum Supported Rust Version
- Multi-Target Considerations
- Get Embedded Property Lists
- Accidental Reuse Protection
- Implementation
- License
- Macros
- Functions
§Motivation
Certain programs require an embedded Info.plist
or launchd.plist
file to
work correctly. For example:
-
Info.plist
is needed to obtain certain permissions on macOS 10.15 and later. -
launchd.plist
is needed to makelaunchd
daemons and user agents.
Doing this manually with include_bytes!
is cumbersome. To understand
why, see the implementation. This library removes the
headache of doing this.
§Usage
This library is available on crates.io and can be used by adding
the following to your project’s Cargo.toml
:
[dependencies]
embed_plist = "1.2"
…and this to any source file in your crate:
embed_plist::embed_info_plist!("Info.plist");
// If making a daemon:
embed_plist::embed_launchd_plist!("launchd.plist");
Done! It’s that simple. 🙂
See implementation for details on this sorcery.
§Minimum Supported Rust Version
This library targets 1.39 as its minimum supported Rust version (MSRV).
Requiring a newer Rust version is considered a breaking change and will
result in a “major” library version update. In other words: 0.1.z
would
become 0.2.0
, or 1.y.z
would become 2.0.0
.
§Multi-Target Considerations
This library only works for Mach-O
binaries. When building a cross-platform program, these macro calls should
be placed behind a #[cfg]
to prevent linker errors on other targets.
#[cfg(target_os = "macos")]
embed_plist::embed_info_plist!("Info.plist");
§Get Embedded Property Lists
After using these macros, you can get their contents by calling
get_info_plist
or get_launchd_plist
from anywhere in your program.
We can verify that the result is correct by checking it against reading the appropriate file at runtime:
embed_plist::embed_info_plist!("Info.plist");
let embedded_plist = embed_plist::get_info_plist();
let read_plist = std::fs::read("Info.plist")?;
assert_eq!(embedded_plist, read_plist.as_slice());
If the appropriate macro has not been called, each function creates a compile-time error by failing to reference the symbol defined by that macro:
let embedded_plist = embed_plist::get_info_plist();
§Accidental Reuse Protection
Only one copy of Info.plist
or launchd.plist
should exist in a binary.
Accidentally embedding them multiple times would break tools that read these
sections.
Fortunately, this library makes reuse a compile-time error! This protection works even if these macros are reused in different modules.
embed_plist::embed_info_plist!("Info.plist");
embed_plist::embed_info_plist!("Info.plist");
This example produces the following error:
error: symbol `_EMBED_INFO_PLIST` is already defined
--> src/main.rs:4:1
|
4 | embed_plist::embed_info_plist!("Info.plist");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
Warning: Although the name
_EMBED_INFO_PLIST
can be seen here, you should not reference this symbol with
e.g. an
extern "C"
block. I reserve the right to change this name in a SemVer-compatible
update.
§Implementation
Files are read using include_bytes!
. This normally places data in
__TEXT,__const
, where immutable data is kept. However, property list data
is expected to be in __TEXT,__info_plist
or __TEXT,__launchd_plist
. This
section will explain how I accomplish that.
We begin by reading the file from disk:
const BYTES: &[u8] = include_bytes!("Info.plist");
A naïve approach is to do the following:
#[used] // Prevent optimizing out
#[link_section = "__TEXT,__info_plist"]
static PLIST: &[u8] = BYTES;
This doesn’t work because only the slice’s pointer and length that are
placed in __TEXT,__info_plist
. The referenced bytes are still placed in
__TEXT,__const
.
Instead, we need to arrive at the following:
#[used]
#[link_section = "__TEXT,__info_plist"]
static PLIST: [u8; N] = *BYTES;
We can get N
by using len
. As of Rust 1.39, it is possible to get the
length of a slice within a const
.
const N: usize = BYTES.len();
The next step is to dereference the bytes into a [u8; N]
.
There are two approaches:
-
Call
include_bytes!
again.This is not the approach used by this library because of concerns about compile performance. See the second approach for what this library does.
The following is all we need:
#[used] #[link_section = "__TEXT,__info_plist"] static PLIST: [u8; N] = *include_bytes!("Info.plist");
This works because
include_bytes!
actually returns a&[u8; N]
. It’s often used as a&[u8]
because we don’t know the size when calling it. -
Dereference our current bytes through pointer casting.
This is tricker than the first approach (and somewhat cursed). If you know me, then it’s predictable I’d go this route.
We can get a pointer to the bytes via
as_ptr
, which is usable inconst
:const PTR: *const [u8; N] = BYTES.as_ptr() as *const [u8; N];
Unfortunately, this pointer can’t be directly dereferenced in Rust 1.39 (minimum supported version).
ⓘ#[used] #[link_section = "__TEXT,__info_plist"] static PLIST: [u8; N] = unsafe { *PTR };
Instead, we must cast the pointer to a reference.
You may want to reach for
transmute
, which was stabilized for use inconst
in Rust 1.46. However, earlier versions need to be supported, so that is not an option for this library.This bitwise cast can be accomplished with a
union
:union Transmute { from: *const [u8; N], into: &'static [u8; N], } const REF: &[u8; N] = unsafe { Transmute { from: PTR }.into };
Finally, we can dereference our bytes:
#[used] #[link_section = "__TEXT,__info_plist"] static PLIST: [u8; N] = *REF;
§License
This project is released under either:
at your choosing.
Macros§
- Embeds the
Info.plist
file at$path
directly in the current binary. - Embeds the
Info.plist
file in&[u8]
directly in the current binary. - Embeds the
launchd.plist
file at$path
directly in the current binary. - Embeds the
launchd.plist
file in&[u8]
directly in the current binary.
Functions§
- Returns the contents of the embedded
Info.plist
file. - Returns the contents of the embedded
launchd.plist
file.